package qu.sol2bpel.protocol;

import java.util.ArrayList;
import java.util.StringTokenizer;

import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.RuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.misc.NotNull;
import org.antlr.v4.runtime.tree.ParseTreeProperty;

import qu.sol2bpel.sol.grammar.SOL_FunctionalBaseListener;
import qu.sol2bpel.sol.grammar.SOL_FunctionalParser;
import qu.sol2bpel.sol.grammar.symbolTable.CheckSymbols;
import qu.sol2bpel.sol.grammar.symbolTable.CymbolParser;
import qu.sol2bpel.sol.grammar.symbolTable.FunctionSymbol;
import qu.sol2bpel.sol.grammar.symbolTable.GlobalScope;
import qu.sol2bpel.sol.grammar.symbolTable.LocalScope;
import qu.sol2bpel.sol.grammar.symbolTable.Scope;
import qu.sol2bpel.sol.grammar.symbolTable.Symbol;
import qu.sol2bpel.sol.grammar.symbolTable.Symbol.Type;
import qu.sol2bpel.sol.grammar.symbolTable.VariableSymbol;

public class ProtocolFunctionalListener extends SOL_FunctionalBaseListener {

	Protocol protocol;  
	ParseTreeProperty<Scope> scopes = new ParseTreeProperty<Scope>();
	GlobalScope globals;
	Scope currentScope; // define symbols in this scope
	ArrayList<String> arg_List = new ArrayList<String>();
    ArrayList<String> expr_left_hand_side = new ArrayList<String>();
    String currentFunctionCall;
    String currentParty;
    String currentVar;
    ArrayList<String> functionCall_params= new ArrayList<String>();
	boolean var_Decl= false;
	boolean isSimpleStmt = false;
	boolean isSetupSection = false;
	String currentAtom ;
	
	public ProtocolFunctionalListener(Protocol p)
	{
		protocol = new Protocol(p);

	}
	public Protocol getProtocol() {
		return protocol;
	}

	public void setProtocol(Protocol protocol) {
		this.protocol = protocol;
	}

	@Override public void exitProtocol_definition(@NotNull SOL_FunctionalParser.Protocol_definitionContext ctx) 
	{
		protocol.setProtocol_name(ctx.NAME().getText());	
	}

	@Override public void enterFuncdef(@NotNull SOL_FunctionalParser.FuncdefContext ctx) 
	{
		currentScope = new LocalScope(currentScope);
		saveScope(ctx, currentScope);
	}

	@Override public void exitFuncdef(@NotNull SOL_FunctionalParser.FuncdefContext ctx) 
	{
		System.out.println(currentScope);
		currentScope = currentScope.getEnclosingScope(); // pop scope
		arg_List = new ArrayList<String>();
	}

	@Override public void enterSuite(@NotNull SOL_FunctionalParser.SuiteContext ctx) 
	{
		currentScope = new LocalScope(currentScope);
		saveScope(ctx, currentScope);
	}

	@Override public void exitSuite(@NotNull SOL_FunctionalParser.SuiteContext ctx) 
	{
		System.out.println(currentScope);
		currentScope = currentScope.getEnclosingScope(); // pop scope
	}

	void saveScope(ParserRuleContext ctx, Scope s) { scopes.put(ctx, s); }

	@Override public void enterFunctional_section(@NotNull SOL_FunctionalParser.Functional_sectionContext ctx) 
	{ 
		globals = new GlobalScope(null);
		currentScope = globals; 
	}

	@Override public void exitFunctional_section(@NotNull SOL_FunctionalParser.Functional_sectionContext ctx) 
	{ 
		System.out.println("Scopes :"+ globals );
	}

	@Override public void enterMap_stmt(@NotNull SOL_FunctionalParser.Map_stmtContext ctx) 
	{

	}

	@Override public void exitMap_stmt(@NotNull SOL_FunctionalParser.Map_stmtContext ctx) 
	{
		defineVar(Symbol.Type.tDYNAMIC, ctx.NAME().getSymbol());
	}

	@Override public void enterJoin_stmt(@NotNull SOL_FunctionalParser.Join_stmtContext ctx) 
	{

	}

	@Override public void exitJoin_stmt(@NotNull SOL_FunctionalParser.Join_stmtContext ctx) 
	{
		defineVar(Symbol.Type.tDYNAMIC, ctx.NAME(0).getSymbol());
	}
	public static Symbol.Type getType(int tokenType) {
		switch ( tokenType ) {
		case SOL_FunctionalParser.MATRIX_DATATYPE :  return Symbol.Type.tMATRIX;
		case  SOL_FunctionalParser.VECTOR_DATATYPE :   return Symbol.Type.tVEC;
		case  SOL_FunctionalParser.SIMPLE_DATATYPE : return Symbol.Type.tINT;
		}
		return Symbol.Type.tINVALID;
	}

	@Override public void exitParameters(@NotNull SOL_FunctionalParser.ParametersContext ctx) 
	{
		for(int i=0; i< ctx.strongtypedargslist().stfpdef().size(); i++)
			defineVar(ProtocolFunctionalListener.getType(ctx.strongtypedargslist().stfpdef(i).DATATYPE().getSymbol().getType()),ctx.strongtypedargslist().stfpdef(i).NAME().getSymbol());
		arg_List = new ArrayList<String>();
	}
	
	@Override public void enterVariable(@NotNull SOL_FunctionalParser.VariableContext ctx) 
	{

	}

	@Override public void exitVariable(@NotNull SOL_FunctionalParser.VariableContext ctx) 
	{
		if(var_Decl)
		{
			defineVar(Symbol.Type.tDYNAMIC, ctx.atom().NAME().getSymbol()); 
			currentVar = ctx.atom().NAME().getText();
			expr_left_hand_side.add(currentVar);
		}
		else
		{
			String name = ctx.atom().NAME().getSymbol().getText();
			boolean defined = checkVarsDefined(ctx);
			if(defined)
				arg_List.add(name);
		}
	}
	@Override public void enterFunctionCall(@NotNull SOL_FunctionalParser.FunctionCallContext ctx) 
	{
		var_Decl = false;
		currentFunctionCall = ctx.atom().NAME().getText();
		

	}

	@Override public void exitFunctionCall(@NotNull SOL_FunctionalParser.FunctionCallContext ctx) 
	{
		if(isSetupSection)
			protocol.addProtocolSetup_Step(currentParty, currentFunctionCall, arg_List, expr_left_hand_side);
		currentParty=protocol.getClient();
		currentFunctionCall = "";
		arg_List = new ArrayList<String>();
		expr_left_hand_side = new ArrayList<String>();
	}
	
	@Override public void enterSmall_stmt(@NotNull SOL_FunctionalParser.Small_stmtContext ctx) 
	{
		if(ctx.NAME() != null)
			currentParty = ctx.NAME().getText();
		else
			currentParty = protocol.getClient();
	}
	public static void error(Token t, String msg)
	{
		System.err.printf("line %d:%d %s\n", t.getLine(), t.getCharPositionInLine(),
				msg);	
	}
	public boolean checkVarsDefined(SOL_FunctionalParser.VariableContext ctx)
	{
		String name = ctx.atom().NAME().getSymbol().getText();
		Symbol var = currentScope.resolve(name);
		if ( var==null ) { // The variable is not in scope in the functional section
			if(!protocol.getInputs().contains(name)) //The variable is not a global input declared in the declarative_section
				{
					ProtocolFunctionalListener.error(ctx.atom().NAME().getSymbol(), "no such variable: "+name);
					return false;
				}
		}
		if ( var instanceof FunctionSymbol ) {
			ProtocolFunctionalListener.error(ctx.atom().NAME().getSymbol(), name+" is not a variable");
			return false;
		}
		return true;
	}

	@Override public void enterSimple_stmt(@NotNull SOL_FunctionalParser.Simple_stmtContext ctx) 
	{
		isSimpleStmt = true;
	}
	
	@Override public void exitSimple_stmt(@NotNull SOL_FunctionalParser.Simple_stmtContext ctx) 
	{
		isSimpleStmt = false;
	}
	@Override public void enterSetup_stats(@NotNull SOL_FunctionalParser.Setup_statsContext ctx) 
	{
		isSetupSection = true;
	}
	@Override public void exitSetup_stats(@NotNull SOL_FunctionalParser.Setup_statsContext ctx) 
	{
		isSetupSection = false;		
	}
	@Override public void enterObject_FunctionCall(@NotNull SOL_FunctionalParser.Object_FunctionCallContext ctx) 
	{
		var_Decl = false;
		if( ctx.trailer(0) != null)
			currentFunctionCall = ctx.trailer(0).NAME().getText();
	}

	@Override public void exitObject_FunctionCall(@NotNull SOL_FunctionalParser.Object_FunctionCallContext ctx) 
	{
		if(isSetupSection)
			protocol.addProtocolSetup_Step(currentParty, currentFunctionCall, arg_List, expr_left_hand_side);
		currentParty=protocol.getClient();
		currentFunctionCall = "";
		arg_List = new ArrayList<String>();
		expr_left_hand_side = new ArrayList<String>();
	}

	@Override public void enterExpr_stmt(@NotNull SOL_FunctionalParser.Expr_stmtContext ctx) 
	{
		if(ctx.testlist_star_expr().size()>1) 
			var_Decl = true; //It is a variable declaration or assignment
		

		
		//System.out.println("ctx child count:" +ctx.getChildCount()+ " ctx stop: "+ ctx.getParent().getText());
		//System.out.println("ctx start:" +ctx.start +" ctx stop: "+ ctx.stop +ctx.getText()+" ctx child count: "+ ctx.getChildCount()+ " ctx "+ctx.getPayload().getText());
		//ctx start:[@34,95:96='A1',<87>,5:1] ctx stop: [@41,108:108=')',<92>,5:14]A1,A2=split(A) ctx child count: 3 ctx A1,A2=split(A)
		//ctx.getPayload().getText() equivalent to ctx.getText() equivalent to ctx.getParent().getText()
		//System.out.println("ctx child 0: "+ ctx.getChild(0).getText());//A1,A2
		//System.out.println("ctx : "+ ctx.getChild(0).getChildCount());//A1,A2 => 3
		//System.out.println("ctx : "+ ctx.getText());//A1,A2
	}

	@Override public void exitExpr_stmt(@NotNull SOL_FunctionalParser.Expr_stmtContext ctx) 
	{
		if(ctx.testlist_star_expr().size()==1 && isSimpleStmt) //function call
		{
			
		}
		/*

			if(ctx.getChild(0).getText().contains(","))
			{
				StringTokenizer tokenizer = new StringTokenizer(ctx.getChild(0).getText(),",");
				int i=0;
				while( tokenizer.hasMoreTokens())
				{
					String var = tokenizer.nextToken();
					System.out.println(var);

					int start = i; int end= tokenizer.hasMoreTokens()? i+1 : ctx.testlist_star_expr(0).getChildCount();
					System.out.println(ctx.testlist_star_expr(0).test().));
				defineVar(Symbol.Type.tDYNAMIC, ctx.testlist_star_expr(0).getToken(start, end).getSymbol());
				i+=2;
				}
			}
		}*/

	}

	@Override public void enterTrailer(@NotNull SOL_FunctionalParser.TrailerContext ctx) { }

	@Override public void exitTrailer(@NotNull SOL_FunctionalParser.TrailerContext ctx)
	{
		//if(ctx.start.getText().equals("."))
		//	defineVar(Symbol.Type.tDYNAMIC, ctx.NAME().getSymbol());
	}

	@Override public void enterAtom(@NotNull SOL_FunctionalParser.AtomContext ctx) { }

	@Override public void exitAtom(@NotNull SOL_FunctionalParser.AtomContext ctx) 
	{
		currentAtom = ctx.NAME().getText();
		if(protocol.getCloud_parties().contains(currentAtom))
			currentParty = currentAtom;
		//	defineVar(Symbol.Type.tDYNAMIC, ctx.NAME().getSymbol());
	}

	void defineVar(Symbol.Type varType, Token nameToken) {
		Symbol.Type type = varType;
		VariableSymbol var = new VariableSymbol(nameToken.getText(), type);
		currentScope.define(var); // Define symbol in current scope
	}
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub

	}

}
